package controller;

import exceptions.NameTakenException;
import exceptions.NotDebugModeException;
import exceptions.NotLeaderException;
import gComEngine.Model;

import java.awt.EventQueue;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.rmi.ConnectException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.UnknownHostException;
import java.rmi.server.ServerNotActiveException;
import java.util.Properties;
import java.util.Vector;
import java.util.concurrent.LinkedBlockingQueue;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import view.GComChatGUI;
import view.GComDebugGUI;

import communicationModule.MessageObject;

/**
 * The backbone of the demonstration application GCom Chat. An object of this
 * class manages communication between the Views(Debug/Chat-GUI) and the GCom
 * model. This class is responsible for instantiating each subsystem and
 * configure each subsystem to suit user supplied information. Such as Nickname,
 * Groupname and Nameserver address necessary for the GCom model to instantiate.
 * 
 */
public class Controller implements Serializable {

    private static final long serialVersionUID = 8844043160047292023L;

    private GComChatGUI chatGui;
    private GComDebugGUI debugGui;
    private Model model;
    private String[] nameservers;
    private final Controller control;
    private final Properties sessionProp = new Properties();
    private final LinkedBlockingQueue<String> outputQueue = new LinkedBlockingQueue<String>();
    private final LinkedBlockingQueue<String[]> inputQueue = new LinkedBlockingQueue<String[]>();
    private ChangeListener clientListChangeListener;
    private ChangeListener debugHoldbackListChangeListener;
    private ChangeListener orderingHoldbackChangeListener;
    private ChangeListener receivedFilterChangeListener;

    public Controller(final Boolean debugmode) {

        control = this;
        try {
            initGUI();
        } catch (final InvocationTargetException e) {
            e.printStackTrace();
        } catch (final InterruptedException e) {
            e.printStackTrace();
        }

        setupProperties(debugmode);
        if (sessionProp.containsKey("debug")) {
            initDebugGUI();
        }
        initWorkers();

        buildListeners();
        // the main thread will be processing work in the engine/model.
        initModel();
        // update the initial display of chat clients.
        if (sessionProp.containsKey("debug")) {
            debugGui.setDebuggerTitle("GCom Debug Client : "
                    + sessionProp.getProperty("nickname"));
        }

        try {
            // start engine
            model.processWork();
        } catch (final RemoteException e) {
            e.printStackTrace();
        } catch (final NotBoundException e) {
            e.printStackTrace();
        } catch (final ServerNotActiveException e) {
            e.printStackTrace();
        } catch (final NullPointerException e) {
            e.printStackTrace();
            chatGui.errorPrompt("Failed to initialize GCom middleware", true);
        }
    }

    private void buildListeners() {
        clientListChangeListener = new ChangeListener() {
            @Override
            public void stateChanged(final ChangeEvent e) {
                chatGui.setClientList(control.getGroupMembers());
            }
        };
        if (sessionProp.containsKey("debug")) {
            debugHoldbackListChangeListener = new ChangeListener() {
                @Override
                public void stateChanged(final ChangeEvent e) {
                    debugGui.setDebugHoldbackData(control
                            .debugGetDebugHoldback());
                }
            };
            orderingHoldbackChangeListener = new ChangeListener() {
                @Override
                public void stateChanged(final ChangeEvent e) {
                    debugGui.setOrderingHoldbackData(control
                            .debugGetOrderingHoldback());
                }
            };
            receivedFilterChangeListener = new ChangeListener() {
                @Override
                public void stateChanged(final ChangeEvent e) {
                    debugGui.setReceivedData(control.debugGetReceivedBuffer());
                }
            };
        }
    }

    private void setupProperties(final Boolean debugmode) {
        if (debugmode) {
            sessionProp.setProperty("debug", "true");
        }
        final String[] connectionData = chatGui.getConnectionData();
        sessionProp.setProperty("nickname", connectionData[1]);
        sessionProp.setProperty("groupname", connectionData[2]);
        sessionProp.setProperty("multicast", connectionData[3]);
        sessionProp.setProperty("ordering", connectionData[4]);
        nameservers = connectionData[0].split(",");

    }

    private void initWorkers() {
        // start the threads processing messages from model/gui.
        new Thread(new OutputQueueHandler(outputQueue, control)).start();
        new Thread(new InputQueueHandler(inputQueue, control)).start();
    }

    private void initGUI() throws InterruptedException,
            InvocationTargetException {
        EventQueue.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                try {
                    chatGui = new GComChatGUI(outputQueue);
                    chatGui.setVisible(true);
                } catch (final Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void initDebugGUI() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    debugGui = new GComDebugGUI(control);
                    debugGui.setVisible(true);
                } catch (final Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void initModel() {
        try {
            model = new Model(sessionProp, inputQueue, nameservers,
                    clientListChangeListener, debugHoldbackListChangeListener,
                    orderingHoldbackChangeListener,
                    receivedFilterChangeListener);
        } catch (final UnknownHostException e) {
            // bad nameserver hostname supplied
            chatGui.errorPrompt("Failed to connect to nameservers: "
                    + nameservers, true);
        } catch (final ConnectException e) {
            // bad nameserver address supplied
            chatGui.errorPrompt("Failed to connect to nameservers: "
                    + nameservers, true);
        } catch (final RemoteException e) {
            e.printStackTrace();
        } catch (final NotBoundException e) {
            e.printStackTrace();
        } catch (final ServerNotActiveException e) {
            e.printStackTrace();
        } catch (final NameTakenException e) {
            // nickname taken
            chatGui.errorPrompt("Failed to join group, nickname was taken: "
                    + sessionProp.getProperty("nickname"), true);
        } catch (final NotLeaderException e) {
            /*
             * This normally would not happen. Unless a bad custom
             * implementation is used.
             */
        }
    }

    public Vector<String> getGroupMembers() {
        if (model == null) {
            return null;
        }
        return model.getGroupNicknames();
    }

    protected void submitOutput(final String string) {
        try {
            model.sendMessage(string);
        } catch (final RemoteException e) {
            e.printStackTrace();
        }
    }

    protected void submitInput(final String[] strings) {
        chatGui.printChat(strings);
    }

    public void debugReleaseAll() {
        try {
            model.debugReleaseAll();
        } catch (final NotDebugModeException e) {
            e.printStackTrace();
        }
    }

    public void debugRelease(final Object msg) {
        try {
            model.debugRelease((MessageObject) msg);
        } catch (final NotDebugModeException e) {
            e.printStackTrace();
        }
    }

    public Object[] debugGetDebugHoldback() {
        try {
            return model.debugGetDebugHoldback();
        } catch (final NotDebugModeException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Vector<String> debugGetReceivedBuffer() {
        try {
            return model.debugGetReceivedBuffer();
        } catch (final NotDebugModeException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Object[] debugGetOrderingHoldback() {
        try {
            return model.debugGetOrderingHoldback();
        } catch (final NotDebugModeException e) {
            e.printStackTrace();
        }
        return null;
    }
}
